import edu.ucdavis.genomics.metabolomics.binbase.connector.references.pubchem.PubchemCompoundSDFResolver
import net.sf.jniinchi.JniInchiOutputKey
import org.apache.log4j.Logger

/**
 * a couple of compound helpers
 * User: wohlgemuth
 * Date: Jan 14, 2010
 * Time: 3:46:47 PM
 */
class CompoundHelper {

  /**
   * returns the given compound or stores it in the database and returns the reference
   */

  public static Compound getCompound(String inchi,JniInchiOutputKey key,Logger logger){

    Compound compound = Compound.findByInchi(inchi)

    if (compound == null) {
      logger.debug "no compound found for ${inchi}"
      compound = new Compound()
      compound.inchi = inchi;
      setInchiHashKey(compound, key)
    }
    else {
      logger.info "compound found with id: ${compound.id}"
      setInchiHashKey(compound, key)
    }

    logger.debug "loading compound: ${compound.id}"

    //make sure only one person can access a compound at a given time


    return getCompound(compound.id)
  }

  /**
   * retuns the compound with the specified id
   */
  public static Compound getCompound(long id){
    Compound compound = Compound.lock(id)
    compound.refresh()
    return compound
  }
  /**
   * updates the cas number
   */
  public static void updatesTheCasNumber(String casNumber, Compound compound, Logger logger) {

    logger.debug "update cas for compound ${compound.id}"
    compound = saveCompound(compound, logger)

    //logger.debug "trying to update cas number"
    if (casNumber != null) {
      Object cases = Cas.findAllByCompound(compound)

      if (cases == null) {
        logger.debug "no cas number assigned so far"
        storeCas(casNumber, compound, logger);
      }
      else if (cases instanceof List) {

        if (cases.size() > 0) {

          for (Cas cas: cases) {
            if (cas.casNumber.equals(casNumber)) {
              logger.debug "cas number is already registerd"
              return
            }
          }

          logger.debug "it's a new cas number so we store it"
          storeCas(casNumber, compound, logger);

        }
        else{
          logger.debug "has no cas numbers so far"
          storeCas(casNumber, compound, logger);
          
        }
      }
      else {
        //if the cas number does not exist yet store it
        if (!cases.casNumber.equals(casNumber)) {
          storeCas(casNumber, compound, logger);
        }
        else {
          logger.debug "already stored in the database"
        }
      }
    }
  }

  /**
   * store the cas number
   */
  public static void storeCas(String cas, Compound compound, logger) {
    logger.debug "store cas for compound ${compound.id}"

    //logger.debug "trying to storce cas"
    if (cas != null && cas.equals("Not Available") == false) {
      logger.debug "store cas: ${cas}"
      compound.addToDbLinks(new Cas(casNumber: cas))
    }
    else {
      logger.debug "no cas defined"
    }
  }

  /**
   * stores the compound or updates it and returns a ew locked compound
   */
  public static Compound saveCompound(Compound compound, Logger logger) {
    //logger.debug "trying to save compound"

    logger.debug "saving compound"
    if (compound.save(flush: true) == null) {

      logger.error "an error occured during the save of the compound"
      compound.errors.each {
        logger.error it
      }
      logger.error ""
    }
    else{
      compound = getCompound(compound.id)
      return compound
    }
  }

  /**
   * stores the compound or updates it
   */
  public static void saveHashKey(InchiHashKey key) {
    //logger.debug "trying to save compound"
    if (key.save(flush: true) == null) {

      Logger logger = Logger.getLogger("error")

      logger.error "an error occured during the save of the key"
      key.errors.each {
        logger.error it
      }
      logger.error ""
    }
  }

  /**
   * adds a new synonym
   */
  public static void addSynonym(List<String> syn, Logger logger, Compound compound) {
    logger.debug "generate synonym for compound ${compound.id}"

    compound = saveCompound(compound, logger)

    Object o = Synonym.findAllByCompound(compound)

    //filter synonym
    if (o != null) {
      if (o instanceof Synonym) {
        logger.debug "just a single synonym found for compound ${compound.id}"
        Synonym s = o

        Iterator<String> it = syn.iterator();

        while (it.hasNext()) {
          String name = it.next()

          if (name.equals(s.getName())) {
            it.remove()
          }
        }
      }
      else {

        logger.debug "found a list of synonyms for compound ${compound.id}"
        List<Synonym> syno = o

        for (Synonym s: syno) {
          Iterator<String> it = syn.iterator();

          while (it.hasNext()) {
            String name = it.next()

            if (name.equals(s.getName())) {
              it.remove()
            }
          }
        }
      }
    }
    //save the synonyms now
    for (String s: syn) {
      if (s != null) {
        if (s.trim().size() > 0 && s.equals("Not Available") == false) {
          logger.debug "create synonym ${s}"
          compound.addToSynonyms(new Synonym(name: s.trim()))
        }
      }
    }
  }

  /**
   * updates the sid
   */
  public static void updateSID(Logger logger, Compound compound, int sid) {
    logger.debug "update sid for compound ${compound.id}"

    compound = saveCompound(compound, logger)
//adds to substance field
    
    Object substance = PubchemSubstance.findAllByCompound(compound)
    if (substance == null) {
      logger.debug "adding sid ${sid}"
      compound.addToDbLinks(new PubchemSubstance(sid: sid))
    }
    else {

      if (substance instanceof List) {
        boolean found = false;

        for (PubchemSubstance sub: substance) {
          if (sub.sid == sid) {
            found = true
          }
        }
        if (!found) {
          logger.debug "sid was not found in the list, so it's added ${sid}"
          compound.addToDbLinks(new PubchemSubstance(sid: sid))
        }

      }
      else {
        PubchemSubstance sub = substance

        if (sub.sid == sid) {
          logger.debug "same sid (${sid})"
        }
        else {
          logger.debug "add another id"
          compound.addToDbLinks(new PubchemSubstance(sid: sid))

        }
      }
    }
  }


    /**
     * updates the sid
     */
    public static void updateNCI(Logger logger, Compound compound, String nci) {
      logger.debug "update nci for compound ${compound.id}"

      compound = saveCompound(compound, logger)

      Object substance = NCI.findAllByCompound(compound)

      if (!nci.equals("Not Available")) {
        if (substance == null) {
          logger.debug "adding ${nci}"
          compound.addToDbLinks(new NCI(nciId: nci))
        }
        else {

          if (substance instanceof List) {
            boolean found = false;

            for (NCI sub: substance) {
              if (sub.nciId == nci) {
                found = true
              }
            }
            if (!found) {
              logger.debug "nci was not found in the list, so it's added ${nci}"
              compound.addToDbLinks(new NCI(nciId: nci))
            }

          }
          else {
            NCI sub = substance

            if (sub.nciId == nci) {
              logger.debug "same nci (${nci})"
            }
            else {
              logger.debug "add another id"
              compound.addToDbLinks(new NCI(nciId: nci))

            }
          }
        }
      }
    }

  /**
   * updates the sid
   */
  public static void updateLipidMaps(Logger logger, Compound compound, String lipidMap) {
    logger.debug "update lipidmaps for compound ${compound.id}"

    compound = saveCompound(compound, logger)

    Object substance = LipidMap.findAllByCompound(compound)
    
    if (!lipidMap.equals("Not Available")) {
      if (substance == null) {
        logger.debug "adding ${lipidMap}"
        compound.addToDbLinks(new LipidMap(lipidMapId: lipidMap))
      }
      else {

        if (substance instanceof List) {
          boolean found = false;

          for (LipidMap sub: substance) {
            if (sub.lipidMapId == lipidMap) {
              found = true
            }
          }
          if (!found) {
            logger.debug "lipid map id was not found in the list, so it's added ${lipidMap}"
            compound.addToDbLinks(new LipidMap(lipidMapId: lipidMap))
          }

        }
        else {
          LipidMap sub = substance

          if (sub.lipidMapId == lipidMap) {
            logger.debug "same lipid map id (${lipidMap})"
          }
          else {
            logger.debug "add another lipid map id"
            compound.addToDbLinks(new LipidMap(lipidMapId: lipidMap))

          }
        }
      }
    }
  }

  public static void updateCID(Object substance, Logger logger, Compound compound, int cid) {
    logger.debug "update cid for compound ${compound.id}"

    compound = saveCompound(compound, logger)

    if (substance == null) {
      logger.debug "adding cid ${cid}"
      compound.addToDbLinks(new PubchemCompound(cid: cid))
    }
    else {

      if (substance instanceof List) {
        boolean found = false;

        for (PubchemCompound sub: substance) {
          if (sub.cid.equals(cid)) {
            found = true
          }
        }

        if (!found) {
          logger.debug "cid was not found in the list ${substance}, so it's added ${cid}"
          compound.addToDbLinks(new PubchemCompound(cid: cid))
        }
        else {
          logger.debug "cid is already in the list ${substance}"
        }

      }
      else {
        PubchemCompound sub = substance

        if (sub.cid.equals(cid)) {
          logger.debug "same cid (${cid})"
          sub = new PubchemCompound(cid: cid)
          sub.save(flush: true)
        }
        else {
          logger.debug "add another cid ${cid}"
          compound.addToDbLinks(new PubchemCompound(cid: cid))

        }
      }
    }
  }
  /**
   * updates a cid
   */
  public static void updateCID(Object substance, Logger logger, Compound compound, PubchemCompoundSDFResolver resolver) {
    logger.debug "update cid for compound ${compound.id}"

    compound = saveCompound(compound, logger)
    int cid = Integer.parseInt(resolver.cid)

    if (substance == null) {
      logger.debug "adding cid ${cid}"
      compound.addToDbLinks(buildPubchemCompound(new PubchemCompound(cid: cid), resolver))
    }
    else {

      if (substance instanceof List) {
        boolean found = false;

        for (PubchemCompound sub: substance) {
          if (sub.cid == cid) {
            found = true
          }
        }
        if (!found) {
          logger.debug "cid was not found in the list, so it's added ${cid}"
          compound.addToDbLinks(buildPubchemCompound(new PubchemCompound(cid: cid), resolver))
        }

      }
      else {
        PubchemCompound sub = substance

        if (sub.cid == cid) {
          logger.debug "same cid (${cid})"
          sub = buildPubchemCompound(sub, resolver)
          sub.save(flush: true)
        }
        else {
          logger.debug "add another id"
          compound.addToDbLinks(buildPubchemCompound(new PubchemCompound(cid: cid), resolver))

        }
      }
    }
  }

  /**
   * merges smile codes and stores them
   */
  public static void updateSmiles(String smiles, Compound compound, boolean isometric) {

    List<Smiles> _smiles = null
    if (smiles != null && smiles.equals("Not Available") == false) {
      if (isometric) {
        _smiles = IsomericSmile.findAllByCompound(compound)
      }
      else {
        _smiles = CanonicalSmile.findAllByCompound(compound)
      }

      if (_smiles.isEmpty()) {
        if (isometric) {
          compound.addToSmiles(new IsomericSmile(code: smiles))
        } else {
          compound.addToSmiles(new CanonicalSmile(code: smiles))
        }
      }
      else {

        for (Smiles smile: _smiles) {
          if (smile.code.equals(smiles)) {
            return
          }
        }
        if (isometric) {
          compound.addToSmiles(new IsomericSmile(code: smiles))
        }
        else {
          compound.addToSmiles(new CanonicalSmile(code: smiles))
        }
      }
    }
  }

  /**
   * stores and update iupac names
   */
  public static void updateIUPACNames(List<String> names, Compound compound, Logger logger) {
    logger.debug "update iupac for compound ${compound.id}"

    compound = saveCompound(compound, logger)

    if (names != null) {

      def iupacNames = IUPAC.findAllByCompound(compound)

      if (iupacNames == null) {
        for (String s: names) {
          logger.debug "adding iupac ${s}"
          compound.addToIupac(new IUPAC(name: s))
        }
      }
      else if (iupacNames instanceof IUPAC) {
        for (String s: names) {
          if (s.equals(iupacNames.name) == false) {
            logger.debug "adding iupac ${s}"
            compound.addToIupac(new IUPAC(name: s))
          }
        }
      }
      else {
        for (String s: names) {
          boolean taken = false

          for (IUPAC iupac: iupacNames) {
            if (iupac.name.equals(s)) {
              taken = true
            }
          }

          if (taken == false) {
            logger.debug "adding iupac ${s}"

            compound.addToIupac(new IUPAC(name: s))
          }
        }
      }
    }
  }

  /**
   * builds a basic pubchem compound
   */
  private static PubchemCompound buildPubchemCompound(PubchemCompound com, PubchemCompoundSDFResolver resolver) {

    if (resolver.charge != null)
      com.charge = resolver.charge
    if (resolver.hbondAcceptor != null)
      com.hbondDonor = resolver.hbondDonor
    if (resolver.hbondAcceptor != null)
      com.hbondAcceptor = resolver.hbondAcceptor
    if (resolver.xLogP != null)
      com.experimentalLogP = resolver.xLogP

    return com
  }

  /**
   * updates the HMDB id
   */
  public static void updateHMDB(Object substance, Logger logger, Compound compound, String hmdbId) {
    logger.debug "update hmdb for compound ${compound.id}"

    logger.info "checking id ${hmdbId}"
    compound = saveCompound(compound, logger)

    if (substance == null) {
      logger.debug "adding ${hmdbId}"
      compound.addToDbLinks(new HMDB(hmdbId: hmdbId))
    }
    else {

      if (substance instanceof List) {
        boolean found = false;

        for (HMDB sub: substance) {
          if (sub.hmdbId == hmdbId) {
            found = true
          }
        }
        if (!found) {
          logger.debug "id was not found in the list, so it's added ${hmdbId}"
          compound.addToDbLinks(new HMDB(hmdbId: hmdbId))
        }

      }
      else {
        HMDB sub = substance

        if (sub.hmdbId == hmdbId) {
          logger.debug "same id (${hmdbId})"
        }
        else {
          logger.debug "add another id"
          compound.addToDbLinks(new HMDB(hmdbId: hmdbId))

        }
      }
    }
  }
  public static void updateKegg(Logger logger, Compound compound, String kegg) {
    logger.debug "update kegg for compound ${compound.id}"

    logger.info "checking id ${kegg}"
    compound = saveCompound(compound, logger)

    Object substance = Kegg.findAllByCompound(compound)
    
    if (substance == null) {
      logger.debug "adding ${kegg}"
      compound.addToDbLinks(new Kegg(keggId: kegg))
    }
    else {

      if (substance instanceof List) {
        boolean found = false;

        for (Kegg sub: substance) {
          if (sub.keggId == kegg) {
            found = true
          }
        }
        if (!found) {
          logger.debug "id was not found in the list, so it's added ${kegg}"
          compound.addToDbLinks(new Kegg(keggId: kegg))
        }

      }
      else {
        Kegg sub = substance

        if (sub.keggId == kegg) {
          logger.debug "same id (${kegg})"
        }
        else {
          logger.debug "add another id"
          compound.addToDbLinks(new Kegg(keggId: kegg))

        }
      }
    }
  }

  public static void updateChebiId(Logger logger, Compound compound, String chebiId) {
    logger.debug "update chebi for compound ${compound.id}"

    updateChebiId (Chebi.findAllByCompound(compound),logger,compound,chebiId)
  }
  
  /**
   * updates the HMDB id
   */
  public static void updateChebiId(Object substance, Logger logger, Compound compound, String chebiId) {
    logger.debug "update chebi for compound ${compound.id}"

//adds to substance field
    compound = saveCompound(compound, logger)

    if (substance == null) {
      logger.debug "adding ${chebiId}"
      compound.addToDbLinks(new Chebi(chebiId: chebiId))
    }
    else {

      if (substance instanceof List) {
        boolean found = false;

        for (Chebi sub: substance) {
          if (sub.chebiId == chebiId) {
            found = true
          }
        }
        if (!found) {
          logger.debug "id was not found in the list, so it's added ${chebiId}"
          compound.addToDbLinks(new Chebi(chebiId: chebiId))
        }

      }
      else {
        Chebi sub = substance

        if (sub.chebiId == chebiId) {
          logger.debug "same id (${chebiId})"
        }
        else {
          logger.debug "add another id"
          compound.addToDbLinks(new Chebi(chebiId: chebiId))

        }
      }
    }
  }

  /**
   * updates the HMDB id
   */
  public static void updateMetageneId(Object substance, Logger logger, Compound compound, String metageneId) {
    logger.debug "update metagene for compound ${compound.id}"

    compound = saveCompound(compound, logger)

    if (substance == null) {
      logger.debug "adding ${metageneId}"
      compound.addToDbLinks(new Metagene(metageneId: metageneId))
    }
    else {

      if (substance instanceof List) {
        boolean found = false;

        for (Metagene sub: substance) {
          if (sub.metageneId == metageneId) {
            found = true
          }
        }
        if (!found) {
          logger.debug "id was not found in the list, so it's added ${metageneId}"
          compound.addToDbLinks(new Metagene(metageneId: metageneId))
        }

      }
      else {
        Metagene sub = substance

        if (sub.metageneId == metageneId) {
          logger.debug "same id (${metageneId})"
        }
        else {
          logger.debug "add another id"
          compound.addToDbLinks(new Metagene(metageneId: metageneId))

        }
      }
    }
  }

  public static void updateMetlinId(Object substance, Logger logger, Compound compound, String metlinId) {
    logger.debug "update metlin for compound ${compound.id}"

    compound = saveCompound(compound, logger)

    if (substance == null) {
      logger.debug "adding ${metlinId}"
      compound.addToDbLinks(new Metlin(metlinId: metlinId))
    }
    else {

      if (substance instanceof List) {
        boolean found = false;

        for (Metlin sub: substance) {
          if (sub.metlinId == metlinId) {
            found = true
          }
        }
        if (!found) {
          logger.debug "id was not found in the list, so it's added ${metlinId}"
          compound.addToDbLinks(new Metlin(metlinId: metlinId))
        }

      }
      else {
        Metlin sub = substance

        if (sub.metlinId == metlinId) {
          logger.debug "same id (${metlinId})"
        }
        else {
          logger.debug "add another id"
          compound.addToDbLinks(new Metlin(metlinId: metlinId))

        }
      }
    }
  }

  /**
   * sets the inchi hash key
   */
  public static void setInchiHashKey(Compound compound, JniInchiOutputKey inchiKey) {

    if (compound.inchiHashKey == null) {

      InchiHashKey hashKey = new InchiHashKey(compound: compound)

      hashKey.version = inchiKey.getFlagVersion()

      char[] checkChar = new char[1]
      checkChar[0] = inchiKey.getCheckChar()
      hashKey.checkChar = new String(checkChar)

      char[] flagChar = new char[1]
      flagChar[0] = inchiKey.getFlagChar()
      hashKey.flagChar = new String(flagChar)


      hashKey.completeKey = inchiKey.getKey()
      hashKey.firstBlock = inchiKey.getFirstBlock()
      hashKey.secondBlock = inchiKey.getSecondBlock()
      hashKey.flagFixedH = inchiKey.getFlagFixedH()
      hashKey.flagIsotopic = inchiKey.getFlagIsotopic()
      hashKey.flagStereo = inchiKey.getFlagStereo()

      hashKey.compound = compound
      compound.inchiHashKey = hashKey

      saveHashKey(hashKey)
    }

  }
}
